package com.my.valve.v2;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by ZhongGang
 * at 2018/9/14 15:49
 */
public class Valve {
    private static final Logger LOGGER = LoggerFactory.getLogger(Valve.class);

    private int permits;
    private long time;
    private int count;
    private long start;
    private long nextStart;
    private ReentrantLock lock = new ReentrantLock();
    private Condition available = lock.newCondition();

    public Valve(int permits, long time, TimeUnit unit) {
        Objects.requireNonNull(unit);

        if (permits < 1) {
            throw new IllegalArgumentException("permits must be greater than zero");
        }

        if (time < 1) {
            throw new IllegalArgumentException("time must be greater than zero");
        }

        this.permits = permits;
        this.time = unit.toMillis(time);
    }

    public int limit() {
        try {
            this.lock.lock();

            long current = System.currentTimeMillis();

            this.count += 1;

            if (this.count == 1) {
                this.start = current;
                this.nextStart = this.start + this.time;
                return this.count;
            }

            if (current >= this.nextStart) {
                this.count = 1;
                this.start = this.nextStart;
                this.nextStart = this.start + this.time;
                return this.count;
            }

            if (this.count > this.permits) {
                long rest = this.nextStart - current;
                if (rest > 0) {
                    try {
//                        Thread.sleep(rest);
                        TimeUnit.MILLISECONDS.sleep(rest);
//                        this.available.await(rest, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                this.start = this.nextStart;
                this.nextStart = this.start + this.time;
                this.count = 1;
//                return this.limit();
            }

            return this.count;
        } finally {
            this.lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        Valve valve = new Valve(10, 5, TimeUnit.SECONDS);

        for (int i = 0; i < 21; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int limit = valve.limit();
                    LOGGER.info("passed thread: {}, count: {}", Thread.currentThread().getName(), limit);
                }
            }).start();

            Thread.sleep(1000);
        }

    }
}
